# coding:utf-8

import datetime
import json
import time
import urllib
from urllib.parse import urljoin

import os
# NOTE by chenhj, 请勿随便移动此requests库的导入位置, 否则会引发SSLContext无限循环的bug
# ref: https://github.com/kennethreitz/grequests/issues/103
import requests
from abc import ABCMeta, abstractstaticmethod
from sqlalchemy.sql import text

from common.backends import (
    VoucherStat, BidStockStat, DemandStat, RenewableStat, DemandBackend
)
from common.bid_notify.message import MessageNotify
from common.bid_notify.utils import JPush
from common.constant import SMS_CONFIG, ZONE_STAT_EXCHANGE, MEMBERS_PHONE
from common.im_util import im_client
from common.log import Log
from common.models import model_factory
from common.producer import Producer
from common.schedule.seven_moor_tasks import seven_moor_get_audio_file_task
from common.utils import generate_mengwang_pwd, DB, redis_client
from common.wechat_util import get_wxuser_info


class ConsumerBase(object):
    """
    created by chenhj at 2017/7/25
    消费者基类, 需要实现msg_call_back方法
    """
    __metaclass__ = ABCMeta

    @staticmethod
    @abstractstaticmethod
    def msg_callback(channel, method, properties, body):
        """
        created by chenhj at 2017/7/25
        消息处理方法
        """
        raise NotImplementedError


class SingleSmsConsumerBase(object):
    """
    created by chenhj at 2017/11/10

    """
    userid = None
    pwd = None
    url = None

    @classmethod
    def msg_callback(cls, channel, method, properties, body):
        url = cls.url or SMS_CONFIG['SMS_URL']
        timestamp = datetime.datetime.strftime(
            datetime.datetime.now(), '%m%d%H%M%S')
        ciphertext_pwd = generate_mengwang_pwd(cls.userid, cls.pwd, timestamp)
        tmp_body = json.loads(body)
        mobile = tmp_body['mobile']
        # 梦网的content需要先GBK编码，再urlencode
        content = tmp_body['content']
        content = content.encode('GBK')
        tmp_content = urllib.parse.urlencode({'content': content})
        content = tmp_content.split('=')[-1]

        data = {
            'userid': cls.userid,
            'pwd': ciphertext_pwd,
            'mobile': mobile,
            'content': content,
            'timestamp': timestamp,
        }
        try:
            response = requests.post(
                url,
                headers={'Content-Type': 'application/json'},
                data=json.dumps(data),
            )
            Log.info(response.content.decode('utf-8'))
        except Exception as e:
            Log.info(e)


class CaptchaSmsConsumer(SingleSmsConsumerBase):
    """
    add by zhouhl 2017-07-26
    单独发送验证码的消费者
    """
    userid = SMS_CONFIG['CAPTCHA_USERID']
    pwd = SMS_CONFIG['CAPTCHA_PWD']


class SmsConsumer(SingleSmsConsumerBase):
    """
    created by chenhj at 2017/7/25
    万屏科技签名的短信发送
    """
    userid = SMS_CONFIG['USERID']
    pwd = SMS_CONFIG['PWD']


class WPHSmsConsumer(SingleSmsConsumerBase):
    """
    created by chenhj at 2017/7/25
    万屏汇签名的短信发送
    """
    userid = SMS_CONFIG['WPH_USERID']
    pwd = SMS_CONFIG['WPH_PWD']


class ALLIANCESmsConsumer(SingleSmsConsumerBase):
    """
    created by lina at 2017/7/31
    万屏联盟签名的短信发送
    """
    userid = SMS_CONFIG['ALLIANCE_USERID']
    pwd = SMS_CONFIG['ALLIANCE_PWD']
    url = SMS_CONFIG['ALLIANCE_SMS_URL']


class MultiSmsConsumerBase(object):
    userid = None
    pwd = None

    @classmethod
    def msg_callback(cls, channel, method, properties, body):
        url = SMS_CONFIG['MULTI_SMS_URL']

        timestamp = datetime.datetime.strftime(
            datetime.datetime.now(), '%m%d%H%M%S')
        ciphertext_pwd = generate_mengwang_pwd(cls.userid, cls.pwd, timestamp)
        tmp_multimt = json.loads(body)
        # 梦网的content需要先GBK编码，再urlencode
        multimt = []
        for i in tmp_multimt:
            tmp_i = {}
            tmp_i['mobile'] = i['mobile']
            content = i['content'].encode('GBK')
            tmp_content = urllib.parse.urlencode({'content': content})
            tmp_i['content'] = tmp_content.split('=')[-1]
            multimt.append(tmp_i)

        data = {
            'userid': cls.userid,
            'pwd': ciphertext_pwd,
            'multimt': multimt,
            'timestamp': timestamp,
        }
        try:
            response = requests.post(
                url,
                headers={'Content-Type': 'application/json'},
                data=json.dumps(data),
            )
            Log.info(response.content.decode('utf-8'))
        except Exception as e:
            Log.info(e)


class MultiSmsConsumer(MultiSmsConsumerBase):
    """
    created by zhouhl at 2017-07-05
    梦网群发短信消费者(LED屏签名)
    """
    userid = SMS_CONFIG['MULTI_USERID']
    pwd = SMS_CONFIG['MULTIPWD']


class WPHMultiSmsConsumer(MultiSmsConsumerBase):
    """
    created by zhouhl at 2017-07-05
    梦网群发短信消费者(万屏汇签名)
    """
    userid = SMS_CONFIG['WPH_USERID']
    pwd = SMS_CONFIG['WPH_PWD']


class WechatUserConsumer(ConsumerBase):
    """
    created by chenhj at 21/03/2018
    用户关注公众号后, 异步同步用户信息
    """
    @staticmethod
    def msg_callback(channel, method, properties, body):
        """
        created by chenhj at 21/03/2018

        """
        Log.info('sync wx user info...')
        Log.info('防止新用户还没commit, 这里沉睡5s')
        time.sleep(5)
        body = json.loads(body)
        openid = body['openid']
        Log.info(openid)
        with DB() as session:
            user_info = get_wxuser_info(openid)
            wxuser = session.query(model_factory.wxuser).filter(
                model_factory.wxuser.openid == openid
            ).first()
            if wxuser:
                wxuser.nickname = user_info['nickname'].encode('utf8')
                wxuser.sex = user_info['sex']
                wxuser.language = user_info['language']
                wxuser.city = user_info['city']
                wxuser.province = user_info['province']
                wxuser.country = user_info['country']
                wxuser.headimgurl = user_info['headimgurl']
                wxuser.subscribe_time = user_info['subscribe_time']
                wxuser.remark = user_info['remark']
                wxuser.groupid = user_info['groupid']
                wxuser.tagid_list = user_info['tagid_list']


class ImConsumer(ConsumerBase):
    """
    add by zhouhl 2017-09-12
    IM同步客户信息消费者
    """
    @staticmethod
    def msg_callback(channel, method, properties, body):
        params = json.loads(body)
        try:
            if 'func' in params:
                getattr(im_client, params['func'])(
                    *params.get('args', tuple()),
                    **params.get('kwargs', dict())
                )
            else:
                # 兼容原代码，避免mq数据问题
                im_client.update_user_info(**params)
        except Exception as e:
            Log.info('ImConsumer failed: {}'.format(repr(e)))


class CommonApiConsumer(ConsumerBase):
    """
    create by ld_wu 2018-06-08
    通用的消费者
    """
    backend = dict(
        jpush=JPush(),
        voucher_stat=VoucherStat(),
        bid_stock_stat=BidStockStat(),
        demand_stat=DemandStat(),
        renewable_stat=RenewableStat(),
        demand_backend=DemandBackend()
    )

    @staticmethod
    def msg_callback(channel, method, properties, body):
        params = json.loads(body)
        try:
            backend = CommonApiConsumer.backend[params['backend']]
            getattr(
                backend,
                params['func'])(
                    *params.get('args', tuple()),
                    **params.get('kwargs', dict())
                )
        except Exception as e:
            Log.info('CommonApiConsumer failed: {}'.format(repr(e)))
            Log.info('body:{}'.format(body))


class IMMsgConsumer(ConsumerBase):
    """
    create by ld_wu 2017-11-27
    im 消息
    """

    @staticmethod
    def msg_callback(channel, method, properties, body):
        params = json.loads(body)
        try:
            getattr(MessageNotify(), params['func'])(
                *params['args'],
                **params['kwargs']
            )
        except Exception as e:
            Log.info('send im message failed: {}'.format(repr(e)))


class SyncUserInfoConsumer(ConsumerBase):
    """
    created by chenhj at 18/12/2017

    """
    @staticmethod
    def msg_callback(channel, method, properties, body):
        """
        created by chenhj at 18/12/2017

        """
        params = json.loads(body)
        user_id = params['user_id']
        mobile = params['mobile']
        Log.info('get phone:{} area'.format(mobile))

        API = r'http://cx.shouji.360.cn/phonearea.php?number={mobile}'
        # 广东的area_id直接写在这里, 不查询了
        GUANGDONG_ID = 440000
        WAR_ZONE_ID = 2

        select_area_sql = """
            select id, war_zone_id from area where name like '{}%' and type = 1
        """

        update_user_sql = text("""
            update user set phone_area_id=:phone_area_id, war_zone_id=:war_zone_id
            where id=:id
        """)

        select_wx_openid_sql = text("""
            select wx_openid from user where id=:id
        """)

        with open(
            os.path.join(
                os.path.dirname(os.path.abspath(__file__)),
                'phone_area.txt'
            ), 'rb'
        ) as f:
            phone_area_dic = json.loads(f.read())

        with DB() as session:
            phone_prefix = mobile[:7]

            if phone_prefix in phone_area_dic:
                Log.info('get phone area from local...')
                prov = phone_area_dic[phone_prefix]
                area = session.execute(
                    select_area_sql.format(prov)
                ).fetchone()
                phone_area_id = area.id
                war_zone_id = area.war_zone_id
            else:
                Log.info('get phone area from 360 api...')
                phone_info = json.loads(requests.get(API.format(mobile=mobile)).content)
                if phone_info['code'] == 0:
                    prov = phone_info['data']['province']
                    area = None
                    if prov:
                        area = session.execute(
                            select_area_sql.format(prov)
                        ).fetchone()
                    phone_area_id = area.id if area else GUANGDONG_ID
                    war_zone_id = area.war_zone_id if area else WAR_ZONE_ID
                else:
                    phone_area_id = GUANGDONG_ID
                    war_zone_id = WAR_ZONE_ID
            session.execute(
                update_user_sql,
                {
                    'id': user_id,
                    'phone_area_id': phone_area_id,
                    'war_zone_id': war_zone_id
                }
            )

            # 新用户关注了微信, 入新增人数统计
            wx_openid_res = session.execute(
                select_wx_openid_sql,
                {'id': user_id}
            ).fetchone()
            if wx_openid_res.wx_openid:
                producer = Producer(ZONE_STAT_EXCHANGE)
                producer.exchange_send(json.dumps({
                    'attend_cnt': 1,
                    'war_zone_id': war_zone_id
                }))


def user_is_stuff(data, session):
    """
    created by chenhj at 13/03/2018
    判断用户是否员工
    """
    user_id = (
        data.get('pay_user_id')
        or data.get('bid_user_id')
        or data.get('return_bid_user_id')
        or data.get('cost_bid_user_id')
        or data.get('dispatch_user_id')
    )
    if not user_id:
        return False

    select_mobile_sql = """
        select mobile from user where id=:id
    """
    data = session.execute(
        select_mobile_sql,
        {'id': user_id}
    ).fetchone()
    return data.mobile in MEMBERS_PHONE if data else False


class WarZoneStatConsumer(ConsumerBase):
    """
    created by chenhj at 19/12/2017

    """
    @staticmethod
    def msg_callback(channel, method, properties, body):
        """
        created by chenhj at 19/12/2017
        处理除人数外的统计(涉及db操作的统一放这里)
        {
            'sales': float(bill.amt),
            'pay_user_id': user.id,
            'war_zone_id': user.war_zone_id
        }
        {
            'ro_cnt': 1,
            'war_zone_id': demand_area.war_zone_id
        }
        {
            'published_count': 1,
            'war_zone_id': demand_area.war_zone_id
        }
        {
            'bid_cnt': 1,
            'bid_user_id': self.user.id,
            'war_zone_id': self.user.war_zone_id
        }
        {
            'return_bid_point': add_point,
            'return_bid_user_id': supplier.id,
            'war_zone_id': supplier.war_zone_id
        }
        {
            'cancel_attend_cnt': 1,
            'war_zone_id': user.war_zone_id
        }
        {
            'attend_cnt': 1,
            'war_zone_id': user.war_zone_id
        }
        {
            'cost_bid_point': points,
            'cost_bid_user_id': user_id,
            'war_zone_id': war_zone_data.war_zone_id
        }
        {
            'cost_bid_point': points,
            'war_zone_id': war_zone_data.war_zone_id
        }
        """
        Log.info('war_zone_stat...')
        Log.info(body)

        data = json.loads(body)
        if not data['war_zone_id']:
            Log.info('no war_zone...')
            return

        # 如果key在这里边, 则update时使用自增
        incr_keys = [
            'sales', 'ro_cnt', 'published_count', 'bid_cnt', 'cost_bid_point',
            'return_bid_point', 'cancel_attend_cnt', 'attend_cnt', 'cost_dispatch_point',
            'locked_cnt', 'return_dispatch_point'
        ]

        # 如果key在这里边, 则update时直接覆盖
        update_keys = [
            'pay_people_cnt', 'bid_people_cnt', 'cost_bid_people_cnt',
            'return_bid_people_cnt', 'people_cnt', 'dispatch_people_cnt', 'return_dispatch_people_cnt'
        ]

        incr_war_zone_sql = """
            update war_zone_daily_statics set {key}={key}+:value, updated_at=UNIX_TIMESTAMP()
             where war_zone_id=:war_zone_id and date=:date
        """

        update_war_zone_sql = """
            update war_zone_daily_statics set {key}=:value, updated_at=UNIX_TIMESTAMP()
             where war_zone_id=:war_zone_id and date=:date
        """

        select_war_zone_data_sql = text("""
            select id from war_zone_daily_statics where war_zone_id=:war_zone_id and date=:date
        """)

        insert_war_zone_sql = text("""
            insert into war_zone_daily_statics(war_zone_id, date, created_at, updated_at, people_cnt)
             values (:war_zone_id, :date, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), :people_cnt)
        """)

        select_people_cnt_sql = text("""
            select count(*) as people_cnt from user
             where war_zone_id=:war_zone_id and wx_openid != '';
        """)

        today = datetime.date.today().strftime('%F')
        war_zone_id = data['war_zone_id']
        with DB() as session:
            if user_is_stuff(data, session):
                return

            # 先判断是否存在今日数据. 若无, 先插入
            war_zone_data = session.execute(
                select_war_zone_data_sql,
                {
                    'war_zone_id': war_zone_id,
                    'date': today
                }
            ).fetchone()
            if not war_zone_data:
                people_cnt_data = session.execute(
                    select_people_cnt_sql,
                    {
                        'war_zone_id': war_zone_id
                    }
                ).fetchone()
                # 先插入
                session.execute(
                    insert_war_zone_sql,
                    {
                        'war_zone_id': war_zone_id,
                        'date': today,
                        'people_cnt': people_cnt_data.people_cnt
                    }
                )

            json_incr_keys = data.keys() & incr_keys
            json_update_keys = data.keys() & update_keys
            # 自增的更新
            for each in json_incr_keys:
                session.execute(
                    incr_war_zone_sql.format(key=each),
                    {
                        'value': data[each],
                        'war_zone_id': war_zone_id,
                        'date': today
                    }
                )

            # 覆盖更新
            for each in json_update_keys:
                session.execute(
                    update_war_zone_sql.format(key=each),
                    {
                        'value': data[each],
                        'war_zone_id': war_zone_id,
                        'date': today
                    }
                )


class WarZonePeopleStatConsumer(ConsumerBase):
    """
    created by chenhj at 19/12/2017

    """
    @staticmethod
    def msg_callback(channel, method, properties, body):
        """
        created by chenhj at 19/12/2017
        处理人数相关的统计
        {
            'sales': float(bill.amt),
            'pay_user_id': user.id,
            'war_zone_id': user.war_zone_id
        }
        {
            'bid_cnt': 1,
            'bid_user_id': self.user.id,
            'war_zone_id': self.user.war_zone_id
        }
        {
            'return_bid_point': add_point,
            'return_bid_user_id': supplier.id,
            'war_zone_id': supplier.war_zone_id
        }
        {
            'cancel_attend_cnt': 1,
            'war_zone_id': user.war_zone_id
        }
        {
            'attend_cnt': 1,
            'war_zone_id': user.war_zone_id
        }
        {
            'cost_bid_point': points,
            'cost_bid_user_id': user_id,
            'war_zone_id': war_zone_data.war_zone_id
        }
        {
            'return_dispatch_point': 1,
            'return_dispatch_user_id': user_id,
            'war_zone_id': war_zone_data.war_zone_id
        }
        """
        data = json.loads(body)
        if not data['war_zone_id']:
            Log.info('no war_zone...')
            return
        wechat_keys = ['attend_cnt', 'cancel_attend_cnt']
        # val中的第一个值为redis-key, 第二个值为发往WarZoneStatConsumer的关键字
        other_dic = {
            'pay_user_id': ('pay_user_ids_{zone}_{date}', 'pay_people_cnt'),
            'bid_user_id': ('bid_user_ids_{zone}_{date}', 'bid_people_cnt'),
            'return_bid_user_id': ('return_bid_user_ids_{zone}_{date}', 'return_bid_people_cnt'),
            'return_dispatch_user_id': ('return_dispatch_user_ids_{zone}_{date}', 'return_dispatch_people_cnt'),
            'cost_bid_user_id': ('cost_bid_user_ids_{zone}_{date}', 'cost_bid_people_cnt')
        }

        select_people_cnt_sql = text("""
            select count(*) as people_cnt from user
             where war_zone_id=:war_zone_id and wx_openid != '';
        """)

        today = datetime.date.today().strftime('%F')
        # 缓存时长25h
        cache_timeout = 25 * 60 * 60
        war_zone_id = data['war_zone_id']
        json_wechat_keys = data.keys() & wechat_keys
        json_other_keys = data.keys() & other_dic.keys()

        with DB() as session:
            if user_is_stuff(data, session):
                return

            producer = Producer(ZONE_STAT_EXCHANGE)
            if json_wechat_keys:
                Log.info('update wechat people cnt...')
                # 取出最新的关注人数, 并往WarZoneStatConsumer发送更新数据
                people_cnt_data = session.execute(
                    select_people_cnt_sql,
                    {
                        'war_zone_id': war_zone_id
                    }
                ).fetchone()
                producer.exchange_send(json.dumps({
                    'people_cnt': people_cnt_data.people_cnt,
                    'war_zone_id': war_zone_id
                }))

            for each in json_other_keys:
                # 写入对应redis, 并把当前缓存中set的数量发往WarZoneStatConsumer
                each_data = other_dic[each]
                Log.info('update {}...'.format(each_data[1]))
                redis_key = each_data[0].format(zone=war_zone_id, date=today)
                redis_client.sadd(redis_key, data[each])
                if redis_client.ttl(redis_key) == -1:
                    redis_client.expire(redis_key, cache_timeout)
                producer.exchange_send(json.dumps({
                    each_data[1]: len(redis_client.smembers(redis_key)),
                    'war_zone_id': war_zone_id
                }))


class WarZoneDispatchStatConsumer(ConsumerBase):
    """
    created by chenhj at 11/01/2018

    """
    @staticmethod
    def msg_callback(channel, method, properties, body):
        """
        created by chenhj at 11/01/2018
        处理派单相关统计
        {
            'cost_dispatch_point': 1,
            'dispatch_user_id': user.user_id,
            'war_zone_id': user.user_war_zone_id
        }
        """
        data = json.loads(body)
        if not data['war_zone_id']:
            Log.info('no war_zone...')
            return
        if 'dispatch_user_id' not in data:
            return

        with DB() as session:
            if user_is_stuff(data, session):
                return

            Log.info('update dispatch_people_cnt...')
            today = datetime.date.today().strftime('%F')
            # 缓存时长25h
            cache_timeout = 25 * 60 * 60
            war_zone_id = data['war_zone_id']
            producer = Producer(ZONE_STAT_EXCHANGE)
            redis_key = 'dispatch_user_id_{zone}_{date}'.format(zone=war_zone_id, date=today)
            redis_client.sadd(redis_key, data['dispatch_user_id'])
            if redis_client.ttl(redis_key) == -1:
                redis_client.expire(redis_key, cache_timeout)
            producer.exchange_send(json.dumps({
                'dispatch_people_cnt': len(redis_client.smembers(redis_key)),
                'war_zone_id': war_zone_id
            }))


class SevenMoorAudioUploadConsumer(ConsumerBase):
    """
    add by zhouhl 2018-01-13
    七陌录音上传消费者
    """
    @staticmethod
    def msg_callback(channel, method, properties, body):
        data = json.loads(body)
        Log.info('*' * 50)
        Log.info(data)
        Log.info('*' * 50)

        # 20180428 废弃，后续操作不再访问api接口，相关信息直接写入数据库
        # SEVEN_MOOR_UPLOAD_HOST = os.environ.get('opc_host') \
        #     or 'http://opc.88ba.com'
        # upload_uri = 'outbound/seven_moor_audio/upload/{mobile}?record_datetime={record_datetime}&call_id={call_id}&date_info={date_info}'
        # upload_url = urljoin(SEVEN_MOOR_UPLOAD_HOST, upload_uri)

        # upload_audio_url = upload_url.format(
        #     mobile=data['mobile'],
        #     record_datetime=data['record_datetime'],
        #     call_id=data['call_id'],
        #     date_info=data['date_info'],
        # )
        # Log.info('*' * 50)
        # Log.info(upload_url)
        # Log.info(upload_audio_url)
        # Log.info('*' * 50)

        # audio_file_name = data['audio_file_name']
        # now = datetime.datetime.utcnow()
        # seven_moor_get_audio_file_task.apply_async(
        #     (
        #         upload_audio_url, data['record_url'],
        #         audio_file_name, data['date_info']
        #     ),
        #     eta=now + datetime.timedelta(seconds=25),
        #     expires=now + datetime.timedelta(seconds=30),
        #     queue='for_seven_moor_queue'
        # )

        # add by zhouhl 20180428 新版实现，在celery任务中完成下载文件与数据写入
        mobile = data['mobile']
        record_datetime = data['record_datetime']
        call_id = data['call_id']
        date_info = data['date_info']
        record_url = data['record_url']
        audio_file_name = data['audio_file_name']

        now = datetime.datetime.utcnow()
        seven_moor_get_audio_file_task.apply_async(
            (
                record_url, audio_file_name, mobile,
                record_datetime, call_id, date_info
            ),
            eta=now + datetime.timedelta(seconds=25),
            expires=now + datetime.timedelta(seconds=30),
            queue='for_seven_moor_queue'
        )


